package com.github.byakkili.bim.core.util;

import cn.hutool.core.util.ReflectUtil;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Guannian Li
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ProtobufUtils {
    /**
     * 方法缓存
     */
    private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
    /**
     * A Printer converts protobuf message to JSON format.
     */
    private static final JsonFormat.Printer PRINTER = JsonFormat.printer().includingDefaultValueFields().omittingInsignificantWhitespace();

    /**
     * 是否支持Protobuf
     *
     * @param clazz Protobuf对象类型
     * @return Boolean
     */
    public static boolean isSupported(Class<?> clazz) {
        return clazz != null && Message.class.isAssignableFrom(clazz);
    }

    /**
     * Protobuf 转 Json
     *
     * @param message Protobuf对象
     * @return Json
     */
    public static String serializeJson(Message message) {
        try {
            return PRINTER.print(message);
        } catch (InvalidProtocolBufferException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 是否支持Protobuf
     *
     * @param json  Json字符串
     * @param clazz Protobuf对象类型
     * @param <T>   Protobuf对象类型
     * @return Boolean
     */
    public static <T extends Message> T deserializeJson(String json, Class<T> clazz) {
        try {
            Method method = getMethod(clazz, "newBuilder");
            Message.Builder builder = ReflectUtil.invokeStatic(method);
            JsonFormat.parser().merge(json, builder);
            @SuppressWarnings("unchecked")
            T message = (T) builder.build();
            return message;
        } catch (InvalidProtocolBufferException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 序列化
     *
     * @param message Protobuf对象
     * @return 字节
     */
    public static byte[] serialize(Message message) {
        return message == null ? null : message.toByteArray();
    }

    /**
     * 反序列化
     *
     * @param bytes 字节
     * @param clazz Protobuf对象类型
     * @param <T>   Protobuf对象类型
     * @return Protobuf对象
     */
    public static <T extends Message> T deserialize(byte[] bytes, Class<T> clazz) {
        Method method = getMethod(clazz, "parseFrom", byte[].class);
        if (method == null) {
            return null;
        }
        @SuppressWarnings("PrimitiveArrayArgumentToVarargsMethod")
        T message = ReflectUtil.invokeStatic(method, bytes);
        return message;
    }

    /**
     * 获取类方法
     *
     * @param clazz      Protobuf对象类型
     * @param <T>        Protobuf对象类型
     * @param methodName 方法名
     * @param paramTypes 方法参数类型
     * @return 类方法
     */
    private static <T extends Message> Method getMethod(Class<T> clazz, String methodName, Class<?>... paramTypes) {
        String key = clazz.getName() + "." + methodName;
        Method method = METHOD_CACHE.get(key);
        if (method == null) {
            method = ReflectUtil.getPublicMethod(clazz, methodName, paramTypes);
            if (method != null) {
                Method previousValue = METHOD_CACHE.putIfAbsent(key, method);
                if (previousValue != null) {
                    method = previousValue;
                }
            }
        }
        return method;
    }
}
